5.01. Типы данных в JS
Типы данных в JS
Автоматические преобразования (coercion)
Объекты как ссылочные типы
Переменные и константы в процессе использования, получают какое-то значение. Когда мы указываем, что у нас будет возраст (age), мы понимаем, что это будет какое-то число в диапазоне от 0 до 150, к примеру. А для имени (name) будет уже довольно обширный набор символов с разным количеством – от одного до пятидесяти (а может, и больше?). И для устройства это важно, определить участок памяти для небольшого значения (age) или большого (name) – и это определяется типом данных, который даёт системе указание выделить нужный объем ресурсов.
★ JavaScript – язык с динамической типизацией (тип переменной определяется автоматически), поэтому можно просто объявить переменную, не указывая, какого типа она будет, и система сама поймёт, с каким типом работает. Мы просто можем написать «пусть будет x» - «let x», и когда будет x=10, динамическая типизация позволит определить, что тут хватит Number, если же x=”Какой-то текст”, то небольшим размером Number не обойтись, и тип будет указан как String.
Типы в JS бывают примитивными (хранят одно значение) и ссылочными (хранят сложные структуры).
Примитивные типы
| Тип | Описание | Пример |
|---|---|---|
| Number | Числа (целые и дробные) | let age = 25; |
| String | Строки (текст) | let name = "Анна"; |
| Boolean | Логический (true / false) | let isStudent = true; |
| Undefined | Значение не присвоено | let x; |
| Null | Пустое значение | let y = null; |
| Symbol | Уникальный идентификатор | const id = Symbol("id"); |
Как можно понять, примитивные используются для простых данных, когда это какое-то имя, число, текст, или просто «флажок» вроде «истина» или «ложь».
- Число (Number). Тип Number представляет как целые, так и вещественные числа. Внутренне используется формат IEEE 754 (64-битное число с плавающей точкой). Поддерживает специальные значения: Infinity, -Infinity, NaN.
Простой пример:
let age = 30;
Сложный пример:
const totalPrice = items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
.toFixed(2);
const finalPrice = parseFloat(totalPrice) + (hasDiscount ? -totalPrice * 0.1 : 0);
Здесь значение переменной finalPrice формируется через цепочку операций: агрегация массива объектов (reduce), округление до двух знаков после запятой (toFixed — возвращает строку), преобразование строки обратно в число (parseFloat) и условная скидка. Результат — число, но его получение требует нескольких этапов обработки.
- Булево (Boolean). Принимает два значения: true и false. Также может быть получен неявно через приведение типов (например, !!"text" → true).
Простой пример:
let isActive = true;
Сложный пример:
const isEligible = user.age >= 18 &&
user.hasVerifiedEmail &&
(user.permissions.includes('read') || user.role === 'admin');
Переменная isEligible принимает булево значение, вычисленное на основе нескольких условий: возраст, статус верификации и права доступа. Это типичный случай использования логических выражений для контроля доступа.
- Строка (String). Тип String представляет собой последовательность символов UTF-16. Может создаваться как строковый литерал или через конструктор String(). Простой пример:
let name = "Тимур";
Сложный пример:
const greeting = `Добро пожаловать, ${user.name || 'Гость'}!
Вы вошли ${new Date().toLocaleDateString('ru-RU')} в ${new Date().getHours()}:${String(new Date().getMinutes()).padStart(2, '0')}.
Ваш баланс: ${(user.balance ?? 0).toFixed(2)} ₽.`;
Выше используется шаблонная строка с интерполяцией значений из объекта, операторами нулевого слияния (??) и логического ИЛИ (||), форматированием времени и числовым округлением. Результат — многострочная строка, собранная динамически.
Отдельно важно выделить symbol - в JS они особые, и не относятся к «тексту» вроде строки. Это ярлык, который можно использовать как ключ для свойств объекта, как наклейка на шкаф. Каждый Symbol — уникален, даже если у них одинаковое описание. Даже если два символа называются "id", они не равны. Это как два разных браслета с одинаковой гравировкой — выглядят одинаково, но физически разные.
Представим наглядно.
У нас есть объект user, и мы ему решили добавить свойство, сразу в формате строки:
user.id = "12345";
И в дальнейшем, эту переменную id можно перезаписать, заменив значение - если кто-то укажет user.id = значение, то наше изначальное значение будет перезаписано:
user.id = 23456;
И если мы не хотим, чтобы другие скрипты перезаписали случайно наш код, можем использовать Symbol:
user.id = Symbol("id");
user[id] = 12345;
Теперь наш id не перезапишется, потому что не знают о том, что есть id как Symbol. Символы невидимы в циклах, и не покажутся в переборе. Symbol — это скрытое свойство. Его нельзя автоматически превратить в строку при преобразовании типов.
Но в ряде случаев, можно ссылаться к каким-то сложным объектам, как мы ранее уже упоминали – это ссылочные типы.
Ссылочные типы
| Тип | Описание | Пример |
|---|---|---|
| Object | Объект (ключ: значение). Кстати, JSON это Java Script Object Notation, так что это, фактически, тот же синтаксис. | const user = { name: "Анна" }; |
| Array | Массив (список значений) | const nums = [1, 2, 3]; |
| Function | Функция | function greet() { ... } |
Объект имеет какие-то свойства и значения, допустим name – свойство объекта user. Объект, как мы ранее выяснили, может иметь и свои функции – это будут методы.
Объект — это коллекция пар «ключ-значение». Является ссылочным типом. Все не-примитивы в JS являются объектами (включая массивы и функции).
Простой пример:
let person = { name: "Иван", age: 28 };
Сложный пример:
const config = Object.freeze({
apiUrl: process.env.API_URL || 'https://api.example.com',
timeout: parseInt(process.env.TIMEOUT || '5000', 10),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
retryCount: maxRetries > 0 ? Math.min(maxRetries, 5) : 0
});
Выше в примере мы видим, как создаётся объект конфигурации с использованием переменных окружения, преобразованием типов, условной логикой и вложенностью. Затем он замораживается (Object.freeze) для предотвращения изменений — типичная практика в продвинутых приложениях.
Объекты могут быть базовыми, DOM, пользовательскими и глобальным.
Глобальный объект - это объект, который существует в любом окружении выполнения JavaScript и доступен всегда, везде. Все глобальные переменные, функции, встроенные константы на самом деле являются свойствами этого объекта.
Какой именно объект является глобальным, зависит от окружения:
| Окружение | Глобальный объект |
|---|---|
| Браузер | window |
| Node.js | global |
| Современный JS (в любом окружении) | globalThis |
Как это работает? Такой глобальный объект содержит встроенные глобальные переменные, функции и объекты:
Свойства:
Infinity- бесконечность;NaN- «не число»;undefined- значение undefined;globalThis- ссылка на самого себя.
Встроенные функции:
isNaN()- проверяет, является ли значение NaN;isFinite()- проверяет, является ли значение конечным числом;parseFloat(),parseInt()- парсинг чисел;encodeURI(),decodeURI()- кодировка URL;eval()- выполнение строки как кода.
Встроенные конструкторы (которые на самом деле - функции):
Object- объект;Array- массив;String- строка;Number- число;Boolean- булево значение;Function- функция;Date- дата;RegExp- регулярное выражение;Symbol- уникальный идентификатор;Error,TypeError,SyntaxError- ошибки.
Встроенные объекты:
Math- математические функции;JSON- работа с JSON;Promise- промисы (позже их изучим);console- консоль.
Если объявить переменную без var, let, const в глобальной области - она станет свойством глобального объекта. То есть не пишите вроде x = 42, если не хотите создавать глобальное свойство - добавляйте ключевое слово let или const.
Важно - let, const, class не создают свойства в глобальном объекте, а var создаёт, как и вышеприведенный пример с x = 42.
Массив же используется как набор данных, когда мы хотим расширить набор данных. К примеру, nums – набор чисел, включающий три значения – 1, 2 и 3. Каждый из этих элементов массива имеет индекс, что позволяет обращаться к конкретному значению, допустим, 2.
Массив (Array) — упорядоченная коллекция элементов. Является подтипом объекта, но имеет специфические методы (map, filter, reduce и т.д.).
Простой пример:
let numbers = [1, 2, 3];
Сложный пример:
const filteredUsers = users
.filter(user => user.active)
.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`.trim(),
roleLabel: roleMap[user.role] || 'Неизвестно'
}))
.sort((a, b) => a.fullName.localeCompare(b.fullName));
Здесь выполняется фильтрация активных пользователей, маппинг с расширением данных (включая объединение полей и использование внешнего маппинга ролей), затем сортировка по алфавиту. Результат — новый массив объектов с расширенной информацией.
Функция – это та сама функция, которые мы изучили ранее. Она тоже представляет собой тип данных.
Функция (Function) — это объект первого класса. Может быть передана как аргумент, возвращена из другой функции, храниться в переменной.
Простой пример:
function greet(name) {
return `Привет, ${name}!`;
}
Сложный пример:
const createValidator = (rules) => (data) => {
const errors = [];
for (const [field, rule] of Object.entries(rules)) {
if (!rule(data[field])) {
errors.push(`Поле "${field}" не прошло валидацию.`);
}
}
return errors.length === 0 ? null : errors;
};
const validateUser = createValidator({
email: (v) => /\S+@\S+\.\S+/.test(v),
age: (v) => v >= 18 && v < 120
});
Здесь реализован паттерн каррирования: функция высшего порядка возвращает другую функцию. createValidator абстрагирует логику валидации, позволяя генерировать конкретные валидаторы. Это мощный инструмент функционального программирования.